package com.dbflow5;

import com.dbflow5.config.FlowManager;
import com.dbflow5.database.DatabaseStatement;
import com.dbflow5.database.DatabaseWrapper;
import com.dbflow5.query.NameAlias;
import com.dbflow5.query.Operator;
import com.dbflow5.query.OperatorGroup;
import com.dbflow5.query.SQLOperator;
import com.dbflow5.structure.ChangeAction;
import ohos.data.rdb.ValuesBucket;
import ohos.utils.net.Uri;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Set;

/**
 * Description: Provides some handy methods for dealing with SQL statements. It's purpose is to move the
 * methods away from the [Model] class and let any class use these.
 */
public class SqlUtils{
    private static final char[] hexArray = "0123456789ABCDEF".toCharArray();

    public static final String TABLE_QUERY_PARAM = "tableName";

    /**
     * Constructs a [Uri] from a set of [SQLOperator] for specific table.
     *
     * @param contentAuthority contentAuthority
     * @param modelClass The class of table,
     * @param action     The action to use.
     * @param conditions The set of key-value [SQLOperator] to construct into a uri.
     * @return The [Uri].
     */
    public static Uri getNotificationUri(String contentAuthority,
                                         Class<?> modelClass,
                                         ChangeAction action,
                                         Iterable<SQLOperator> conditions) {
        Uri.Builder uriBuilder = new Uri.Builder().scheme("dbflow")
                .decodedAuthority(contentAuthority)
                .appendDecodedQueryParam(TABLE_QUERY_PARAM, FlowManager.getTableName(modelClass));
        if (action != null) {
            uriBuilder.decodedFragment(action.name());
        }
        if (conditions != null) {
            for (SQLOperator condition : conditions) {
                uriBuilder.appendDecodedQueryParam(Uri.encode(condition.columnName()), Uri.encode(condition.value().toString()));
            }
        }
        return uriBuilder.build();
    }


    /**
     * Constructs a [Uri] from a set of [SQLOperator] for specific table.
     *
     * @param contentAuthority contentAuthority
     * @param modelClass The class of table,
     * @param action     The action to use.
     * @param conditions The set of key-value [SQLOperator] to construct into a uri.
     * @return The [Uri].
     */
    public static Uri getNotificationUri(String contentAuthority,
                                         Class<?> modelClass,
                                         ChangeAction action,
                                         SQLOperator[] conditions) {
        Uri.Builder uriBuilder = new Uri.Builder().scheme("dbflow")
                .decodedAuthority(contentAuthority)
                .appendDecodedQueryParam(TABLE_QUERY_PARAM, FlowManager.getTableName(modelClass));
        if (action != null) {
            uriBuilder.decodedFragment(action.name());
        }
        if (conditions != null && conditions.length > 0) {
            for (SQLOperator condition : conditions) {
                uriBuilder.appendDecodedQueryParam(Uri.encode(condition.columnName()),
                        Uri.encode(condition.value().toString()));
            }
        }
        return uriBuilder.build();
    }

    /**
     * Returns the uri for notifications from model changes
     *
     * @param contentAuthority contentAuthority
     * @param modelClass  The class to get table from.
     * @param action      the action changed.
     * @param notifyKey   The column key.
     * @param notifyValue The column value that gets turned into a String.
     * @return Notification uri.
     */
    public static Uri getNotificationUri(String contentAuthority,
                                         Class<?> modelClass,
                                         ChangeAction action,
                                         String notifyKey,
                                         Object notifyValue) {
        Operator<Object> operator = null;
        if (StringUtils.isNotNullOrEmpty(notifyKey)) {
            operator = Operator.op(new NameAlias.Builder(notifyKey).build()).value(notifyValue);
        }
        return getNotificationUri(contentAuthority, modelClass, action, operator != null? new ArrayList<>((Collection<? extends SQLOperator>) operator) : null);
    }


    /**
     * Drops an active TRIGGER by specifying the onTable and triggerName
     *
     * @param databaseWrapper  DatabaseWrapper
     * @param triggerName The name of the trigger
     */
    public static void dropTrigger(DatabaseWrapper databaseWrapper, String triggerName) {
        databaseWrapper.execSQL("DROP TRIGGER IF EXISTS " + triggerName);
    }

    /**
     * Drops an active INDEX by specifying the onTable and indexName
     *
     * @param databaseWrapper databaseWrapper
     * @param indexName The name of the index.
     */
    public static void dropIndex(DatabaseWrapper databaseWrapper, String indexName) {
        databaseWrapper.execSQL("DROP INDEX IF EXISTS " + StringUtils.quoteIfNeeded(indexName));
    }

    /**
     * Adds [ContentValues] to the specified [OperatorGroup].
     *
     * @param contentValues The content values to convert.
     * @param operatorGroup The group to put them into as [Operator].
     */
    public static void addContentValues(ValuesBucket contentValues, OperatorGroup operatorGroup) {
        Set<String> entries = contentValues.getColumnSet();

        for (String key : entries) {
            operatorGroup.and(Operator.op(new NameAlias.Builder(key).build()).is(contentValues.getObject(key)));
        }
    }

    /**
     * Get the key from the contentValues
     * @param contentValues The object to check existence of.
     * @param key           The key to check.
     * @return The key, whether it's quoted or not.
     */
    public static String getContentValuesKey(ValuesBucket contentValues, String key) {
        String quoted = StringUtils.quoteIfNeeded(key);
        String result;
        if(contentValues.hasColumn(quoted)){
            return quoted == null? "" : quoted;
        }else {
            String stripped = StringUtils.stripQuotes(key);
            if(contentValues.hasColumn(stripped)){
                return  stripped == null? "" : stripped;
            }else {
                throw new IllegalArgumentException("Could not find the specified key in the Content Values object.");
            }
        }
    }

    public static long longForQuery(DatabaseWrapper wrapper, String query) {
        DatabaseStatement statement = wrapper.compileStatement(query);
        return statement.simpleQueryForLong();
    }

    public static String stringForQuery(DatabaseWrapper wrapper, String query) {
        DatabaseStatement statement = wrapper.compileStatement(query);
        return statement.simpleQueryForString();
    }

    public static double doubleForQuery(DatabaseWrapper wrapper, String query) {
        DatabaseStatement statement = wrapper.compileStatement(query);
        return (double) statement.simpleQueryForLong();
    }

    /**
     * Converts a byte[] to a String hex representation for within wrapper queries.
     * @param bytes bytes
     * @return hex String
     */
    public static String byteArrayToHexString(byte[] bytes) {
        if (bytes == null) return "";
        char[] hexChars = new char[bytes.length * 2];
        for (int j = 0; j < bytes.length; j++) {
            int v = (int) bytes[j] & 0xFF;
            hexChars[j * 2] = hexArray[v >>> 4];
            hexChars[j * 2 + 1] = hexArray[v & 0x0F];
        }
        return new String(hexChars);
    }

    public static String sqlEscapeString(String value) {
        StringBuilder buildString = new StringBuilder();
        appendEscapedSQLString(buildString, value);
        return buildString.toString();
    }

    public static void appendEscapedSQLString(StringBuilder sb, String sqlString) {
        sb.append('\'');
        if (sqlString.indexOf('\'') != -1) {
            int length = sqlString.length();
            for (int i = 0;i < length; i++) {
                char c = sqlString.charAt(i);
                if (c == '\'') {
                    sb.append('\'');
                }
                sb.append(c);
            }
        } else
            sb.append(sqlString);
        sb.append('\'');
    }
}
